/**
 * GPL v2 or later
 *
 * I'm sorry, but it only supports FAT12 in 3.5' standard floppy yet.
 *
 * Detail support:
 *   * 1 Sector = 1 Cluster
 *   * 224 entries for root directory.
 *   * 2 FATs
 *   * 1440KiB, 2 Heads, 80 Cylinders, 18 Sectors
 *   * 1 Sector = 512B
 *
 * ... It doesn't support every kind of FAT12 but it's a right FAT12 manager.
 */

/**
 *
 * Some notice for media drives
 *  1. The sector number starts with 1 not 0. (1~18)
 */
/**
 * Including headers
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
 * Constant definition
 */
#define CMD_CNT_MAX 100

/**
 * Functions
 */
void cmd_copy(char **args);
void cmd_boot(char **args);
void error(char *error_msg);
void load_img(char *file_name);
void store_img(char *file_name);
void read_sector_logical(FILE *fp, int pos, int cnt, BYTE *buf);

/**
 * Type definition
 */

enum CommandType
{
	CMD_COPY,
	CMD_BOOT
};

struct CommandList
{
	enum CommandType type;
	char **args;
};

typedef unsigned __int8 BYTE;
typedef unsigned __int16 WORD;
typedef unsigned __int32 DWORD;

typedef void (* CMD_FUN)(char **);

/**
 * Partition Boot Record - PBR
 * The comments on the right of the variables mean the values
 * using in the standard 3.5'' floppy (1440KiB)
 */
#pragma pack(push, 1)
struct PBR_common
{
	BYTE jump_code[3];
	BYTE OEM_name[8];
	WORD bytes_per_sec;
	BYTE sec_per_clus;	// 1
	WORD reserved_sec;	// 1
	BYTE FAT_cnt;		// 2
	WORD root_entries;	// FAT12:224(0xE0), FAT16:512, FAT32:0
	WORD total_sec16;	// 2880
	BYTE media_type;	// floppy:0xF0, fixed:0xF8
	WORD FAT_sec;		// 9
	WORD track_sec;		// 18
	WORD header_cnt;	// 2
	DWORD hidden_sec;	// 0
	DWORD total_sec32;	// 0 when 16bits
};

struct PBR_FAT12_16
{
	struct PBR_common common;
	BYTE drive_num;		// 0x00 for floppy, 0x80 for hard disk
	BYTE reserved;		// 0
	BYTE boot_sign;		// 0x29
	DWORD vol_id;		// 0x12345678 (anything is ok)
	BYTE vol_label[11];	// "NO NAME    "
	BYTE fsys_type[8];	// "FAT12   "

	BYTE sys_resv[448];
	WORD magic_code;	// 0xAA55
};

/** Yet unsupported

struct PBR_FAT32
{

};

*/
#pragma pack(pop)

/**
 * Global variables
 */
char *out_fn = NULL, *in_fn = NULL, *bpr_fn = NULL;
BYTE *bpr;

struct CommandList cmd_list[CMD_CNT_MAX];
int cmd_cnt = 0;

/**
 * File Information
 */

int fat_sec; // 0-based

CMD_FUN CommandFunction[] =
		{
				cmd_copy,
				cmd_boot
		};

/**
 * main
 * parse the arguments and process the requests
 */
int main(int argc, char *argv[])
{
	// Parsing Arguments
	int i;
	for(i = 1; i < argc; i++)
	{
		if(strcmp(argv[i], "-o") == 0)
		{
			if(out_fn != NULL) error("Error: Input option '-i' appeared again.");
			out_fn = argv[++i];
			continue;
		}

		if(strcmp(argv[i], "-i") == 0)
		{
			if(in_fn != NULL) error("Error: Input option '-i' appeared again.");
			in_fn = argv[++i];
			continue;
		}

		// Now here comes a command.
		if(strcmp(argv[i], "copy") == 0)
		{
			cmd_list[cmd_cnt].type = CMD_COPY;
			cmd_list[cmd_cnt].args = argv + 1;
			cmd_cnt++;
			i+=2;
		}
		else if(strcmp(argv[i], "boot") == 0)
		{
			cmd_list[cmd_cnt].type = CMD_BOOT;
			cmd_list[cmd_cnt].args = argv + 1;
			cmd_cnt++;
			i+=1;
		}
	}

	//Doing the main process
	load_img(in_fn);

	for(i = 0; i < cmd_cnt; i++) CommandFunction[cmd_list[i].type](cmd_list[i].args);

	store_img(out_fn);
	return 0;
}

/**
 * load_img
 * Load the disk image file to the global variables
 *
 * TODO: Structure the variables.
 *
 * TODO: To be refactored.
 */
void load_img(char *file_name)
{
	//TODO: Read and parse the input file
	FILE *fp; // loaded file
	fp = fopen(file_name, "rb");
	if (fp == NULL) error("Error: failed to open the input file.");

	bpr = malloc(sizeof(BYTE) * 512);
	if(bpr == NULL) error("Error: failed to allocate necessary memory.");
	if(fread(bpr, 512, 1, fp) != 512) error("Error: Incorrect BPR.");

	//TODO: support all kinds of fat
	fat_sec = ((struct PBR_FAT12_16 *)bpr)->common.reserved_sec + 1;

	int fat_size;
	fat_size = ((struct PBR_FAT12_16 *)bpr)->common.FAT_sec * ((struct PBR_FAT12_16 *)bpr)->common.bytes_per_sec;

	BYTE *FAT_compressed;
	FAT_compressed = malloc(sizeof(BYTE) * ((struct PBR_FAT12_16 *)bpr)->common.bytes_per_sec * ((struct PBR_FAT12_16 *)bpr)->common.FAT_sec);
	if(FAT_compressed == NULL) error("Error: failed to allocate necessary memory.");

	read_sector_logical(fp, fat_sec, ((struct PBR_FAT12_16 *) bpr)->common.FAT_sec, FAT_compressed);

	//TODO: decompress FAT


	free(FAT_compressed);

	fclose(fp);
}

void store_img(char *file_name)
{
	//TODO: Write the output file
	free(bpr);
}

/**
 * args[0] = Source
 * args[1] = Dest
 */
void cmd_copy(char **args)
{
	//TODO: Copy file from/to floppy

}

void cmd_boot(char **args)
{
	//TODO: Write a BPR
	//TODO: Later, PBR/BPR will also be supported.

}

/**
 * Argument 'pos' is 0-based.
 */
void read_sector_logical(FILE *fp, int pos, int cnt, BYTE *buf)
{
	fseek(fp, pos * ((struct PBR_FAT12_16 *)bpr)->common.bytes_per_sec, SEEK_SET);
	fread(buf, cnt, ((struct PBR_FAT12_16 *)bpr)->common.bytes_per_sec, fp);
}

void error(char *error_msg)
{
	fprintf(stderr, error_msg);
	exit(-1);
}
